
// Copyright (C) 2007 Richard Uhler
// This file is part of Esiply.
//
// Esiply is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.


// We wrap everything in a function to provide a private namespace for
// the Date helper functions. That function returns the Date object
// it creates. Note: the anonymous function is evaluated right now,
// and returns the Date function, it is not itself the Date function.

Esiply.set_attributes(this, "Date", Esiply.DONT_ENUM);
Date = function()
{
    // The number of milliseconds per day is
    var msPerDay = 86400000;

    // Ecma 15.9.1.2 Day Number and Time within Day
    // A given time value t belongs to day number
    var day = function(t)
    {
        return Math.floor(t/msPerDay);
    };

    // The remainder is called the time within the day
    var timeWithinDay = function(t)
    {
        return t % msPerDay;
    };

    // Ecma 15.9.1.3 Year Number
    var daysInYear = function(y)
    {
        if (y % 4 != 0)
        {
            return 365;
        }
        if (y % 4 == 0 && y % 100 != 0)
        {
            return 366;
        }
        if (y % 100 == 0 && y % 400 != 0)
        {
            return 365;
        }
        if (y % 400 == 0)
        {
            return 366;
        }
        throw new Error("daysInYear: We got somewhere we shouldn't have");
    };

    // The day number of the first day of year y is given by
    var dayFromYear = function(y)
    {
        return 365*(y-1970) + Math.floor((y-1969)/4)
            - Math.floor((y-1901)/100) + Math.floor((y-1601)/400);
    };

    // The time value of the start of a year is:
    var timeFromYear = function(y)
    {
        return msPerDay * dayFromYear(y);
    };

    // A time value determines a year by:
    var yearFromTime = function(t)
    {
        // the largest integer y (closest to +Infinity)
        // such that timeFromYear(y) <= t.
        // Or, in other words...
        // TODO: is this really right?
        return Math.floor((t/msPerDay + 719527.2425)/365.2425);
    };

    // The leap-year function is 1 for a time within a leap year and otherwise
    // is zero
    var inLeapYear = function(t)
    {
        // 0 if daysInYear(yearFromTime(t)) = 365
        // 1 if daysInYear(yearFromTime(t)) = 366
        return daysInYear(yearFromTime(t)) - 365;
    };

    // Ecma 15.9.1.4 Month Number
    // Months are identified by an integer in th range 0 to 11, inclusive.
    // The mapping MonthFromTime(t) from a time value t to a month number
    // is defined by:
    var monthFromTime = function(t)
    {
        var dwy = dayWithinYear(t);
        var ily = inLeapYear(t);
        if (dwy < 0)
        {
            throw new RangeError("monthFromTime: day within year negative");
        }
        var limits = [31, 59+ily, 90+ily, 120+ily, 151+ily, 181+ily,
                        212+ily, 243+ily, 273+ily, 304+ily, 334+ily,
                        365+ily];
        for (var i = 0; i < limits.length; i++)
        {
            if (dwy < limits[i])
            {
                return i;
            }
        }
        throw new RangeError("monthFromTime: day within year more than 366: " + dwy);
    };

    // where 
    var dayWithinYear = function(t)
    {
        return day(t) - dayFromYear(yearFromTime(t));
    };

    var months = ["January", "February", "March", "April",
                    "May", "June", "July", "August", "September",
                    "October", "November", "December"];

    // Ecma 15.9.1.5 Date Number
    // A date number is identified by an integer in the range 1 through 31,
    // inclusive. The mapping DateFromTime(t) from a time value t to a month
    // number is defined by:
    var dateFromTime = function(t)
    {
        debug("dateFromTime: enter");
        var dwy = dayWithinYear(t);
        debug("dateFromTime: dwy: " + dwy + "\n");
        var ily = inLeapYear(t);
        debug("dateFromTime: ily: " + ily + "\n");
        var numbers = [dwy+1, dwy-30, dwy-58-ily, dwy-89-ily,
            dwy-119-ily, dwy-150-ily, dwy-180-ily,
            dwy-211-ily, dwy-242-ily, dwy-272-ily,
            dwy-303-ily, dwy-333-ily];
        return numbers[monthFromTime(t)];
    };

    // Ecma 15.9.1.6 Week Day
    // The weekday for a particular time value t is defined as
    var weekDay = function(t)
    {
        return (day(t)+4)%7;
    };

    var weekdays = ["Sunday", "Monday", "Tuesday",
            "Wednesday", "Thursday", "Friday", "Saturday"];

    // Ecma 15.9.1.8 Local Time Zone Adjustment
    // Some number we have to figure out
    var localTZA = Esiply.local_tza;

    // Ecma 15.9.1.9 Daylight Saving Time adjustment
    // TODO: how do I do this?
    var daylightSavingTA = Esiply.daylight_saving_ta;

    // Ecma 15.9.1.9 Local Time
    // Conversion from UTC to local time is defined by
    var localTime = function(t)
    {
        return t + localTZA + daylightSavingTA(t);
    };

    // Conversion the other way is given by
    var utc = function(t)
    {
        return t - localTZA
            - daylightSavingTA(t - localTZA);
    };

    // Ecma 15.9.1.10 Hours, Minutes, Second, and Milliseconds
    var hourFromTime = function(t)
    {
        return Math.floor(t/msPerHour) % hoursPerDay;
    };

    var minFromTime = function(t)
    {
        return Math.floor(t/msPerMinute) % minutesPerHour;
    };

    var secFromTime = function(t)
    {
        return Math.floor(t/msPerSecond) % secondsPerMinute;
    };

    var msFromTime = function(t)
    {
        return t % msPerSecond;
    };

    // Where
    var hoursPerDay = 24;
    var minutesPerHour = 60;
    var secondsPerMinute = 60;
    var msPerSecond = 1000;
    var msPerMinute = 60000;
    var msPerHour = 3600000;

    // Ecma 15.9.1.11 MakeTime (hour, min, sec, ms)
    // The operator MakeTime calculates a number of milliseconds from its
    // four arguments, which must be number values.
    var makeTime = function(hour, min, sec, ms)
    {
        // 1. If any of the args aren't finite, return NaN
        if (!isFinite(hour)
                || !isFinite(min)
                || !isFinite(sec)
                || !isFinite(ms))
        {
            return NaN;
        }

        // 2. Call toInteger (hour)
        // 3. Call toInteger (min)
        // 4. Call toInteger (sec)
        // 5. Call toInteger (ms)
        hour = Math.floor(hour);
        min = Math.floor(min);
        sec = Math.floor(sec);
        ms = Math.floor(ms);

        // 6. Compute result(2) * msPerHour + res(3)*msPerMinute + res(4)*msPerSec
        // + res(5)
        // 7. Return that result
        return hour * msPerHour + min * msPerMinute + sec * msPerSec + ms;
    };

    // Ecma 15.9.1.12 MakeDay(year, month, date)
    // The operator MakeDay calculates a number of days from its three arguments,
    // which must be ECMAScript number values.
    var makeDay = function(year, month, date)
    {
        // 1. If any of the args aren't finite, return NaN
        if (!isFinite(year) || !isFinite(month) || !isFinite(date))
        {
            return NaN;
        }

        // 2. Call toInteger (year)
        // 3. Call toInteger (month)
        // 4. Call toInteger (date)
        year = Math.floor(year);
        month = Math.floor(month);
        date = Math.floor(date);

        // 5. Compute res(2) + floor(res3/12)
        var res5 = year + Math.floor(month/12);

        // 6. Compute res(3) mod 12
        var res6 = month % 12;

        // 7 Find a value t such that yearFromTime(t) == res5 and
        // monthFromTime(t) == res6 and dateFromTime(t) == 1. But if
        // this isn't possible (because some arg is out of range), return
        // NaN. TODO: hows that?
        var res7;

        // 8. Compute day(res7) + res4-1
        // 9. Return that
        return day(res7) + date - 1;
    };

    // Ecma 15.9.1.13 MakeDate(day, time)
    // The operator MakeDate calculates a number of milliseconds from its two
    // arguments, which must be numbers.
    var makeDate = function(day, time)
    {
        // 1. If day is not finite, or time is not finite, return NaN
        if (!isFinite(day) || !isFinite(time))
        {
            return NaN;
        }

        // 2. Compute day*msPerDay + time
        // 3. Return that.
        return day * msPerDay + time;
    };

    // Ecma 15.9.1.14 timeClip(time)
    // The operator timeClip calculates a number of milliseconds from its 
    // argument, which must be a number value
    var timeClip = function(time)
    {
        // 1. If time is not finite, return NaN
        if (!isFinite(time))
        {
            return NaN;
        }

        // 2. If abs(time) > 8.64e15, return NaN
        if (Math.abs(time) > 8.64e15)
        {
            return NaN;
        }

        // 3. Return toInteger(time)
        return Math.floor(time);
    };

    
    // Here's our local Date function, which we will end up returning.
    var Date = Esiply.create_janus_function(

        // Ecma 15.9.2.1 Date (...)
        // All of the arguments are optional; any arguments supplied are
        // accepted BUT ARE COMPLETELY IGNORED. A string is created and
        // returned as if by the the expression (new Date()).toString().
        function()
        {
            return (new Date()).toString();
        },

        // Ecma 15.9.3.1.
        function()
        {
            // [[prototype]] is set to the original Date prototype object,
            // [[class]] is set to "Date"
            var obj = new Object();
            Esiply.set_prototype(obj, Date.prototype);
            Esiply.set_class(obj, "Date");

            // The value depends on the args
            if (arguments.length == 0)
            {
                // Ecma 15.9.3.3
                // [[value]] is set to the current time (UTC)
                Esiply.set_value(obj, Esiply.get_current_time());
            }
            else if (arguments.length == 1)
            {
                // Ecma 15.9.3.2 new Date(value)
                // 1. Call toPrimitive(value)
                var value = arguments[0];
                value = Esiply.to_primitive(value);

                // 2. If type(value) is string, go to step 5
                if (typeof value == "string")
                {
                    // 5. Parse result(1) as a date, in exactly the same manner
                    // as for the parse method. Let v be the time value for this date.
                    value = Date.parse(value);

                    // 6. go to step 4
                }
                else
                {
                    // 3. let v be toNumber(result1)
                    value = +value;
                }
                // 4. set the [[value]] property to timeClip(v) and return
                Esiply.set_value(obj, timeClip(value));
            }
            else
            {
                // Ecma 15.9.3.1 new Date(year, month, etc...)
                // 1-7:
                var year = +arguments[0];
                var month = +arguments[1];
                var date = (arguments[2] == undefined) ? 1 : +arguments[2];
                var hours = (arguments[3] == undefined) ? 0 : +arguments[3];
                var minutes = (arguments[4] == undefined) ? 0 : +arguments[4];
                var seconds = (arguments[5] == undefined) ? 0 : +arguments[5];
                var ms = (arguments[6] == undefined) ? 0 : +arguments[6];

                // 8. if year is not NaN and 0 < toInteger(year) < 99, res8
                // is 1900 + toInteger(year); else res8 is res1
                year = (!isNaN(year) && 0 < Math.floor(year)
                        && Math.floor(year) < 99) ? 1900 + Math.floor(year) : year;

                // 9. Compute makeDay(year, month, date)
                var res9 = makeDay(year, month, date);

                // 10. Compute makeTime(4, 5, 6, 7)
                var res10 = makeTime(hours, minutes, seconds, ms);

                // 11. Compute makeDate(9, 10)
                var val = makeDate(res9, res10);

                // 12. set [[value]] to timeClip(UTC(val))
                Esiply.set_value(obj, timeClip(utc(val)));
            }
            return obj;
        }
    );

    // Ecma 15.9.4.1 Date.prototype
    // The initial value of Date.prototype is the built-in Date prototype
    // object This property has the attributes {DontEnum, DontDelete,
    // ReadOnly}.  Ecma 15.9.5 The Date prototype object is itself a Date
    // object ([[class]] is "Date") whose value is NaN. The internal
    // [[prototype]] is the Object prototype.
    Date.prototype = {};
    Esiply.set_class(Date.prototype, "Date");
    Esiply.set_value(Date.prototype, NaN);
    Esiply.set_attributes(Date, "prototype",
            Esiply.DONT_ENUM | Esiply.DONT_DELETE | Esiply.READ_ONLY);

    // Ecma 15.9.4.2 Date.parse(string)
    // TODO: implement this
    Esiply.set_attributes(Date, "parse", Esiply.DONT_ENUM);
    Date.parse = function(string)
    {
        throw new Error("Date.parse: Not Yet Implemented");
    };

    // Ecma 15.9.4.3 Date.UTC (year,month[,date[,hours[,minutes[,seconds[,ms]])
    // When the UTC function is called with 2-7 arguments, it computes the date
    // from year, month and (optionally) date, hours, minutes, seconds and ms
    Esiply.set_attributes(Date, "UTC", Esiply.DONT_ENUM);
    Date.UTC = function(year, month, date, hours, minutes, seconds, ms)
    {
        // 1. Call toNumber year
        year = +year;

        // 2. Call toNumber month
        month = +month;

        // 3. If date is supplied use toNumber(date); else use 1
        date = arguments.length > 2 ? +date : 1;

        // 4. If hours is supplied use toNumber(hours); else use 0
        hours = arguments.length > 3 ? +hours : 0;

        // 5. If minutes is supplied use toNumber(minutes); else use 0
        minutes = arguments.length > 4 ? +minutes : 0;

        // 6. If seconds is supplied use toNumber(seconds); else use 0.
        seconds = arguments.length > 5 ? +seconds : 0;

        // 7. If ms is supplied use toNumber(ms); else use 0.
        ms = arguments.length > 6 ? +ms : 0;

        // 8. If res1 is not NaN and 0 <= toInt(res(1)) <= 99, result(8)
        // is 1900 + toInt(res1); else res8 is res1
        if (!isNaN(year) && 0 <= Math.floor(year) && Math.floor(year) <= 99)
        {
            year = 1900 + Math.floor(year);
        }

        // 9. Compute makeDay(year, month, date)
        var md = makeDay(year, month, date);

        // 10. Compute makeTime(hours, minutes, seconds, ms)
        var mt = makeTime(hours, minutes, seconds, ms);

        // 11. Return timeClip(makeDate(md, mt))
        return timeClip(makeDate(md, mt));
    };

    // Ecma 15.9.5.1 Date.prototype.constructor
    // The initial value of Date.prototype.constructor is the built-in Date
    // constructor
    Esiply.set_attributes(Date.prototype, "constructor", Esiply.DONT_ENUM);
    Date.prototype.constructor = Date;

    // Ecma 15.9.5.2 Date.prototype.toString()
    // This function returns a string value. The contents of the string are
    // implementation-dependant, but are intended to represent the current
    // time zone in a convenient, human-readable form.
    Esiply.set_attributes(Date.prototype, "toString", Esiply.DONT_ENUM);
    Date.prototype.toString = function()
    {
        return this.toDateString() + " " + this.toTimeString();
    };

    // Ecma 15.9.5.3 Date.prototype.toDateString()
    // This function returns a string value. The contents of the string 
    // are implementation-dependent, but are intended to represent the
    // "date" portion of the Date in the current time zone in a 
    // convenient, human-readable form.
    Esiply.set_attributes(Date.prototype, "toDateString", Esiply.DONT_ENUM);
    Date.prototype.toDateString = function()
    {
        var str = "";

        // The week day
        var weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
        str += weekdays[this.getDay()] + " ";
        debug(str + "\n");

        // The month
        var months = ["Jan", "Feb", "Mar", "Apr", "May",
            "Jun", "Jul", "Aug", "Sep", "Nov", "Dec"];
        str += months[this.getMonth()] + " ";
        debug(str + "\n");

        // The day
        str += this.getDate() + " ";
        debug(str + "\n");

        // The full year
        str += this.getFullYear();

        return str;
    };

    // Ecma 15.9.5.4 Date.prototype.toTimeString
    // This function returns a string value. The contents of the string
    // are implementation-dependent, but are intended to represent the 
    // "time" portion of the Date in the current time zone in a convenient,
    // human-readable form.
    Esiply.set_attributes(Date.prototype, "toTimeString", Esiply.DONT_ENUM);
    Date.prototype.toTimeString = function()
    {
        var str = "";

        // The hours
        str += this.getHours() + ":";
        debug(str + "\n");

        // The minutes
        str += this.getMinutes() + ":";
        debug(str + "\n");

        // The seconds
        str += this.getSeconds() + " ";
        debug(str + "\n");

        // TODO: return a time zone abbreviation
        str += "UTC";
        debug(str + "\n");

        return str;
    };

    // Ecma 15.9.5.8 Date.prototype.valueOf()
    // The valueOf function returns a number, which is this time value.
    Esiply.set_attributes(Date.prototype, "valueOf", Esiply.DONT_ENUM);
    Date.prototype.valueOf = function()
    {
        return Esiply.get_value(this);
    };

    // Ecma 15.9.5.9 Date.prototype.getTime()
    Esiply.set_attributes(Date.prototype, "getTime", Esiply.DONT_ENUM);
    Date.prototype.getTime = function()
    {
        if (Esiply.get_class(this) != "Date")
        {
            throw new TypeError("getTime called on non-date object");
        }
        return this.valueOf();
    };

    // Ecma 15.9.5.10 Date.prototype.getFullYear()
    Esiply.set_attributes(Date.prototype, "getFullYear", Esiply.DONT_ENUM);
    Date.prototype.getFullYear = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return yearFromTime(localTime(t))
        return yearFromTime(localTime(t));
    };

    // Ecma 15.9.5.11 Date.prototype.getUTCFullYear()
    Esiply.set_attributes(Date.prototype, "getUTCFullYear", Esiply.DONT_ENUM);
    Date.prototype.getUTCFullYear = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return yearFromTime(t)
        return yearFromTime(t);
    };

    // Ecma 15.9.5.12 Date.prototype.getMonth()
    Esiply.set_attributes(Date.prototype, "getMonth", Esiply.DONT_ENUM);
    Date.prototype.getMonth = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return monthFromTime(localTime(t))
        return monthFromTime(localTime(t));
    };

    // Ecma 15.9.5.13 Date.prototype.getUTCMonth()
    Esiply.set_attributes(Date.prototype, "getUTCMonth", Esiply.DONT_ENUM);
    Date.prototype.getUTCMonth = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return monthFromTime(t)
        return monthFromTime(t);
    };

    // Ecma 15.9.5.14 Date.prototype.getDate()
    Esiply.set_attributes(Date.prototype, "getDate", Esiply.DONT_ENUM);
    Date.prototype.getDate = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return dateFromTime(localTime(t))
        return dateFromTime(localTime(t));
    };

    // Ecma 15.9.5.15 Date.prototype.getUTCDate()
    Esiply.set_attributes(Date.prototype, "getUTCDate", Esiply.DONT_ENUM);
    Date.prototype.getUTCDate = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return dateFromTime(t)
        return dateFromTime(t);
    };

    // Ecma 15.9.5.16 Date.prototype.getDay()
    Esiply.set_attributes(Date.prototype, "getDay", Esiply.DONT_ENUM);
    Date.prototype.getDay = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return weekDay(localTime(t))
        return weekDay(localTime(t));
    };

    // Ecma 15.9.5.17 Date.prototype.getUTCDay()
    Esiply.set_attributes(Date.prototype, "getUTCDay", Esiply.DONT_ENUM);
    Date.prototype.getUTCDay = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return weekDay(t)
        return weekDay(t);
    };

    // Ecma 15.9.5.18 Date.prototype.getHours()
    Esiply.set_attributes(Date.prototype, "getHours", Esiply.DONT_ENUM);
    Date.prototype.getHours = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return hourFromTime(localTime(t))
        return hourFromTime(localTime(t));
    };

    // Ecma 15.9.5.19 Date.prototype.getUTCHours()
    Esiply.set_attributes(Date.prototype, "getUTCHours", Esiply.DONT_ENUM);
    Date.prototype.getUTCHours = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return hourFromTime(t)
        return hourFromTime(t);
    };

    // Ecma 15.9.5.20 Date.prototype.getMinutes()
    Esiply.set_attributes(Date.prototype, "getMinutes", Esiply.DONT_ENUM);
    Date.prototype.getMinutes = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return minFromTime(localTime(t))
        return minFromTime(localTime(t));
    };

    // Ecma 15.9.5.21 Date.prototype.getUTCMinutes()
    Esiply.set_attributes(Date.prototype, "getUTCMinutes", Esiply.DONT_ENUM);
    Date.prototype.getUTCMinutes = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return minFromTime(t)
        return minFromTime(t);
    };

    // Ecma 15.9.5.22 Date.prototype.getSeconds()
    Esiply.set_attributes(Date.prototype, "getSeconds", Esiply.DONT_ENUM);
    Date.prototype.getSeconds = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return secFromTime(localTime(t))
        return secFromTime(localTime(t));
    };

    // Ecma 15.9.5.23 Date.prototype.getUTCSeconds()
    Esiply.set_attributes(Date.prototype, "getUTCSeconds", Esiply.DONT_ENUM);
    Date.prototype.getUTCSeconds = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return secFromTime(t)
        return secFromTime(t);
    };

    // Ecma 15.9.5.24 Date.prototype.getMilliseconds()
    Esiply.set_attributes(Date.prototype, "getMilliseconds", Esiply.DONT_ENUM);
    Date.prototype.getMilliseconds = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return msFromTime(localTime(t))
        return msFromTime(localTime(t));
    };

    // Ecma 15.9.5.25 Date.prototype.getMilliseconds()
    Esiply.set_attributes(Date.prototype, "getUTCMilliseconds",
            Esiply.DONT_ENUM);
    Date.prototype.getUTCMilliseconds = function()
    {
        // 1. Let t be this time value.
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return NaN;
        }

        // 3. Return msFromTime(t)
        return msFromTime(t);
    };

    // Ecma 15.9.5.26 Date.prototype.getTimezeonOffset()
    // Returns the difference between local time and UTC time in minutes.
    Esiply.set_attributes(Date.prototype, "getTimezoneOffset",
            Esiply.DONT_ENUM);
    Date.prototype.getTimezoneOffset = function()
    {
        // 1. Let t be this time value
        var t = this.getTime();

        // 2. If t is NaN, return NaN
        if (isNaN(t))
        {
            return t;
        }

        // 3. Return (t - LocalTime(t))/msPerMinute
        return (t - localTime(t))/msPerMinute;
    };

    // Ecma 15.9.5.27 Date.prototype.setTime(time)
    Esiply.set_attributes(Date.prototype, "setTime", Esiply.DONT_ENUM);
    Date.prototype.setTime = function(time)
    {
        // 1. If the this value is not a Date object, throw a TypeError
        // exception.
        if (Esiply.get_class(this) != "Date")
        {
            throw new TypeError("Non-Date object is this of Date.prototype.setTime");
        }

        // 2. Call toNumber(time)
        // 3. Call timeClip(res(2));
        // 4. Set [[value]] property of this value to res3
        // 5. return the [[value]] property of the this value.
        Esiply.set_value(this, timeClip(+time));
        return Esiply.get_value(this);
    };

    // Ecma 15.9.5.28 Date.prototype.setMilliseconds(ms)
    Esiply.set_attributes(Date.prototype, "setMilliseconds", Esiply.DONT_ENUM);
    Date.prototype.setMilliseconds = function(ms)
    {
        // 1. Let t be the result of localTime (this time value)
        var t = localTime(this.getTime());

        // 2. Call toNumber ms
        // 3. Compute makeTime(hourFromTime(t), minFromTime(t), secFromTime(t),
        // result(2)).
        var mt = makeTime(hourFromTime(t), minFromTime(t), secFromTime(t), +ms);

        // 4. Compute utc(makeDate(day(t), res3));
        var value = utc(makeDate(day(t), mt));

        // 5. Set the [[value]] property of the this value to timeClip(res4)
        Esiply.set_value(this, value);

        // 6. Return the [[value]] property of the this value
        return value;
    };

    // Ecma 15.9.5.29 Date.prototype.setUTCMilliseconds(ms)
    Esiply.set_attributes(Date.prototype, "setUTCMilliseconds",
            Esiply.DONT_ENUM);
    Date.prototype.setUTCMilliseconds = function(ms)
    {
        // 1. Let t be this time value
        var t = this.getTime();

        // 2. Call toNumber ms
        // 3. Compute makeTime(hourFromTime(t), minFromTime(t), secFromTime(t),
        // result(2)).
        var mt = makeTime(hourFromTime(t), minFromTime(t), secFromTime(t), +ms);

        // 4. Compute utc(makeDate(day(t), res3));
        var value = utc(makeDate(day(t), mt));

        // 5. Set the [[value]] property of the this value to timeClip(res4)
        Esiply.set_value(this, value);

        // 6. Return the [[value]] property of the this value
        return value;
    };

    // Ecma 15.9.5.30 Date.prototype.setSeconds(sec[,ms])
    Esiply.set_attributes(Date.prototype, "setSeconds", Esiply.DONT_ENUM);
    Date.prototype.setSeconds = function(sec, ms)
    {
        // 1. Let t be the result of localTime(this time value).
        var t = localTime(this.timeValue());

        // 2. Call toNumber sec
        sec = +sec;

        // 3. If ms is not specified, compute msFromTime(t); otherwise
        // call toNumber(ms)
        ms = (ms == undefined) ? msFromTime(t) : +ms;

        // 4. Compute this:
        var mt = makeTime(hourFromTime(t), minFromTime(t), sec, ms);

        // 5. Compute utc (makeDate(day(t), mt));
        // 6. Set the [[value]] property of the this value to timeClip(res5)
        var value = timeClip(utc(makeDate(day(t), mt)));
        Esiply.set_value(this, value);

        // 7. Return the value of the [[value]] property of the this value.
        return value;
    };

    // Ecma 15.9.5.31 Date.prototype.setUTCSeconds(sec[,ms])
    Esiply.set_attributes(Date.prototype, "setUTCSeconds", Esiply.DONT_ENUM);
    Date.prototype.setUTCSeconds = function(sec, ms)
    {
        // 1. Let t be this time value
        var t =this.timeValue();

        // 2. Call toNumber sec
        sec = +sec;

        // 3. If ms is not specified, compute msFromTime(t); otherwise
        // call toNumber(ms)
        ms = (ms == undefined) ? msFromTime(t) : +ms;

        // 4. Compute this:
        var mt = makeTime(hourFromTime(t), minFromTime(t), sec, ms);

        // 5. Compute makeDate(day(t), mt);
        // 6. Set the [[value]] property of the this value to timeClip(res5)
        var value = timeClip(makeDate(day(t), mt));
        Esiply.set_value(this, value);

        // 7. Return the value of the [[value]] property of the this value.
        return value;
    };

    // Ecma 15.9.5.32 Date.prototype.???
    // This section of the Ecma standard seems to be missing. I wonder
    // what cool function we're missing out on.

    // Ecma 15.9.5.33 Date.prototype.setMinutes(min[,sec[,ms]])
    Esiply.set_attributes(Date.prototype, "setMinutes", Esiply.DONT_ENUM);
    Date.prototype.setMinutes = function(min, sec, ms)
    {
        // 1. Let t be localTime(this time value)
        var t = localTime(this.timeValue());

        // 2. Call toNumber min
        min = +min;

        // 3. If sec is not specified, compute secFromTime(t); otherwise
        // call toNumber(sec)
        sec = (sec == undefined) ? secFromTime(t) : +sec;

        // 4. If ms is not specified, compute msFromTime(t); otherwise
        // call toNumber(ms)
        ms = (ms == undefined) ? msFromTime(t) : +ms;

        // 5. Compute this:
        var mt = makeTime(hourFromTime(t), min, sec, ms);

        // 6. Compute utc(makeDate(day(t), mt));
        // 7. Set the [[value]] property of the this value to timeClip(res6)
        var value = timeClip(utc(makeDate(day(t), mt)));
        Esiply.set_value(this, value);

        // 8. Return the value of the [[value]] property of the this value.
        return value;
    };

    // Ecma 15.9.5.34 Date.prototype.setUTCMinutes(min[,sec[,ms]])
    Esiply.set_attributes(Date.prototype, "setUTCMinutes", Esiply.DONT_ENUM);
    Date.prototype.setUTCMinutes = function(min, sec, ms)
    {
        // 1. Let t be this time value
        var t = this.timeValue();

        // 2. Call toNumber min
        min = +min;

        // 3. If sec is not specified, compute secFromTime(t); otherwise
        // call toNumber(sec)
        sec = (sec == undefined) ? secFromTime(t) : +sec;

        // 4. If ms is not specified, compute msFromTime(t); otherwise
        // call toNumber(ms)
        ms = (ms == undefined) ? msFromTime(t) : +ms;

        // 5. Compute this:
        var mt = makeTime(hourFromTime(t), min, sec, ms);

        // 6. Compute makeDate(day(t), mt);
        // 7. Set the [[value]] property of the this value to timeClip(res6)
        var value = timeClip(makeDate(day(t), mt));
        Esiply.set_value(this, value);

        // 8. Return the value of the [[value]] property of the this value.
        return value;
    };

    // Ecma 15.9.5.35 Date.prototype.setHours(hour[,min[,sec[,ms]]])
    Esiply.set_attributes(Date.prototype, "setHours", Esiply.DONT_ENUM);
    Date.prototype.setHours = function(hour, min, sec, ms)
    {
        // 1. Let t be localTime(this time value)
        var t = localTime(this.timeValue());

        // 2. Call toNumber hour
        hour = +hour;

        // 3. If min is not specified, compute minFromTime(t); otherwise
        // call toNumber(min)
        min = (min == undefined) ? minFromTime(t) : +min;

        // 4. If sec is not specified, compute secFromTime(t); otherwise
        // call toNumber(sec)
        sec = (sec == undefined) ? secFromTime(t) : +sec;

        // 5. If ms is not specified, compute msFromTime(t); otherwise
        // call toNumber(ms)
        ms = (ms == undefined) ? msFromTime(t) : +ms;

        // 6. Compute this:
        var mt = makeTime(hour, min, sec, ms);

        // 7. Compute utc(makeDate(day(t), mt));
        // 8. Set the [[value]] property of the this value to timeClip(res7)
        var value = timeClip(utc(makeDate(day(t), mt)));
        Esiply.set_value(this, value);

        // 9. Return the value of the [[value]] property of the this value.
        return value;
    };

    // Ecma 16.9.5.35 Date.prototype.setUTCHours(hour[,min[,sec[,ms]]])
    Esiply.set_attributes(Date.prototype, "setUTCHours", Esiply.DONT_ENUM);
    Date.prototype.setUTCHours = function(hour, min, sec, ms)
    {
        // 1. Let t be this time value
        var t = this.timeValue();

        // 2. Call toNumber hour
        hour = +hour;

        // 3. If min is not specified, compute minFromTime(t); otherwise
        // call toNumber(min)
        min = (min == undefined) ? minFromTime(t) : +min;

        // 4. If sec is not specified, compute secFromTime(t); otherwise
        // call toNumber(sec)
        sec = (sec == undefined) ? secFromTime(t) : +sec;

        // 5. If ms is not specified, compute msFromTime(t); otherwise
        // call toNumber(ms)
        ms = (ms == undefined) ? msFromTime(t) : +ms;

        // 6. Compute this:
        var mt = makeTime(hour, min, sec, ms);

        // 7. Compute makeDate(day(t), mt);
        // 8. Set the [[value]] property of the this value to timeClip(res7)
        var value = timeClip(makeDate(day(t), mt));
        Esiply.set_value(this, value);

        // 9. Return the value of the [[value]] property of the this value.
        return value;
    };

    // Ecma 15.9.5.36 Date.prototype.setDate(date)
    Esiply.set_attributes(Date.prototype, "setDate", Esiply.DONT_ENUM);
    Date.prototype.setDate = function(date)
    {
        // 1. Let t be the result of localTime(this time value)
        var t = localTime(this.valueOf());

        // 2. Call toNumber (date)
        // 3. Compute makeDay(yearFromTime(t), monthFromTime(t), res2)
        var md = makeDay(yearFromTime(t), monthFromTime(t), +date);

        // 4. Compute utc(makeDate(res3, timeWithinDay(t)))
        // 5. Set the [[value]] property of the this value to timeClip(value)
        var value = timeClip(utc(makeDate(md, timeWithinDay(t))));
        Esiply.set_value(this, value);

        // 6. Return [[value]]
        return value;
    };

    // Ecma 15.9.5.37 Date.prototype.setUTCDate(date)
    Esiply.set_attributes(Date.prototype, "setUTCDate", Esiply.DONT_ENUM);
    Date.prototype.setUTCDate = function(date)
    {
        // 1. Let t be this time value
        var t = this.valueOf();

        // 2. Call toNumber (date)
        // 3. Compute makeDay(yearFromTime(t), monthFromTime(t), res2)
        var md = makeDay(yearFromTime(t), monthFromTime(t), +date);

        // 4. Compute makeDate(res3, timeWithinDay(t))
        // 5. Set the [[value]] property of the this value to timeClip(value)
        var value = timeClip(makeDate(md, timeWithinDay(t)));
        Esiply.set_value(this, value);

        // 6. Return [[value]]
        return value;
    };

    // Ecma 15.9.5.38 Date.prototype.setMonth(month[,date])
    Esiply.set_attributes(Date.prototype, "setMonth", Esiply.DONT_ENUM);
    Date.prototype.setMonth = function(month, date)
    {
        // 1. Let t be the result of localTime(this time value)
        var t = localTime(this.valueOf());

        // 2. Call tonumber(month)
        month = +month;

        // 3. If date is not specified, compute DateFromTime(t); otherwise
        // call toNumber(date)
        date = arguments.length == 1 ? dateFromTime(t) : +date;

        // 4. Compute makeDay(yearFromTime(t), month, date)
        var md = makeDay(yearFromTime(t), month, date);

        // 5. Compute utc(makeDate(md, timeWithinDay(t)))
        // 6. Set the [[value]] property of this value to timeClip(value)
        var value = timeClip(utc(makeDate(md, timeWithinDay(t))));
        Esiply.set_value(this, value);

        // 7. Return [[value]]
        return value;
    };

    // Ecma 15.9.5.39 Date.prototype.setUTCMonth(month[,date])
    Esiply.set_attributes(Date.prototype, "setUTCMonth", Esiply.DONT_ENUM);
    Date.prototype.setUTCMonth = function(month, date)
    {
        // 1. Let t be this time value
        var t = this.valueOf();

        // 2. Call tonumber(month)
        month = +month;

        // 3. If date is not specified, compute DateFromTime(t); otherwise
        // call toNumber(date)
        date = arguments.length == 1 ? dateFromTime(t) : +date;

        // 4. Compute makeDay(yearFromTime(t), month, date)
        var md = makeDay(yearFromTime(t), month, date);

        // 5. Compute makeDate(md, timeWithinDay(t))
        // 6. Set the [[value]] property of this value to timeClip(value)
        var value = timeClip(makeDate(md, timeWithinDay(t)));
        Esiply.set_value(this, value);

        // 7. Return [[value]]
        return value;
    };

    // Ecma 15.9.5.40 Date.prototype.setFullYear(year[,month[,date]])
    Esiply.set_attributes(Date.prototype, "setFullYear", Esiply.DONT_ENUM);
    Date.prototype.setFullYear = function(year, month, date)
    {
        // 1. Let t be localTime(this time value), but if this time value is
        // NaN, let t be +0
        var t = isNan(this.valueOf()) ? +0 : localTime(this.valueOf());

        // 2. Call tonumber(year)
        year= +year;

        // 3. If month is not specified, compute monthFromTime(t); otherwise
        // call toNumber month
        month = arguments.length < 2 ? monthFromTime(t) : +month;

        // 4. If date is not specified, compute DateFromTime(t); otherwise
        // call toNumber(date)
        date = arguments.length < 3 ? dateFromTime(t) : +date;

        // 5. Compute makeDay(year, month, date)
        var md = makeDay(year, month, date);

        // 5. Compute utc(makeDate(md, timeWithinDay(t)))
        // 6. Set the [[value]] property of this value to timeClip(value)
        var value = timeClip(utc(makeDate(md, timeWithinDay(t))));
        Esiply.set_value(this, value);

        // 7. Return [[value]]
        return value;
    };

    // Ecma 15.9.5.41 Date.prototype.setUTCFullYear(year[,month[,date]])
    Esiply.set_attributes(Date.prototype, "setUTCFullYear", Esiply.DONT_ENUM);
    Date.prototype.setUTCFullYear = function(year, month, date)
    {
        // 1. Let t be this time value, but if this time value is NaN,
        // let t be +0
        var t = isNan(this.valueOf()) ? +0 : this.valueOf();

        // 2. Call tonumber(year)
        year= +year;

        // 3. If month is not specified, compute monthFromTime(t); otherwise
        // call toNumber month
        month = arguments.length < 2 ? monthFromTime(t) : +month;

        // 4. If date is not specified, compute DateFromTime(t); otherwise
        // call toNumber(date)
        date = arguments.length < 3 ? dateFromTime(t) : +date;

        // 5. Compute makeDay(year, month, date)
        var md = makeDay(year, month, date);

        // 5. Compute makeDate(md, timeWithinDay(t))
        // 6. Set the [[value]] property of this value to timeClip(value)
        var value = timeClip(makeDate(md, timeWithinDay(t)));
        Esiply.set_value(this, value);

        // 7. Return [[value]]
        return value;
    };

    // Ecma 15.9.5.42 Date.prototype.toUTCString()
    // This function returns a string value. The contents of the string 
    // are implementation-dependent, but are intended to represent the
    // Date in a convenient, human-readable form in UTC.
    Esiply.set_attributes(Date.prototype, "toUTCString", Esiply.DONT_ENUM);
    Date.prototype.toUTCString = function()
    {
        var str = "";

        // The week day
        var weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
        str += weekdays[this.getUTCDay()] + " ";

        // The month
        var months = ["Jan", "Feb", "Mar", "Apr", "May",
            "Jun", "Jul", "Aug", "Sep", "Nov", "Dec"];
        str += months[this.getUTCMonth()] + " ";

        // The day
        str += this.getUTCDate() + " ";

        // The full year
        str += this.getUTCFullYear() + " ";

        // The hours
        str += this.getUTCHours() + ":";

        // The minutes
        str += this.getUTCMinutes() + ":";

        // The seconds
        str += this.getUTCSeconds() + " ";
        str += "UTC";
        return str;
    };

    return Date;
}();

